package io.kiki.stack.netty.protocol;

import io.kiki.stack.netty.command.AbstractCommand;
import io.kiki.stack.netty.command.RequestCommand;
import io.kiki.stack.netty.command.ResponseCommand;
import io.kiki.stack.netty.config.switches.ProtocolSwitch;
import io.kiki.stack.netty.util.CrcUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;


@Slf4j
@Setter
@Getter
public class EncoderImpl implements Encoder {


    @Override
    public void encode(ChannelHandlerContext channelHandlerContext, Serializable serializable, ByteBuf byteBuf) throws Exception {
        try {
            if (serializable instanceof AbstractCommand) {
                /*
                 * proto: magic code for protocol
                 * ver: version for protocol
                 * type: request/response/request oneway
                 * cmdcode: code for remoting command
                 * ver2:version for remoting command
                 * requestId: id of request
                 * codec: code for codec
                 * switch: function switch
                 * (req)timeout: request timeout.
                 * (resp)respStatus: response status
                 * classLen: length of request or response class name
                 * headerLen: length of header
                 * cotentLen: length of content
                 * className
                 * header
                 * content
                 * crc (optional)
                 */
                int index = byteBuf.writerIndex();
                AbstractCommand abstractCommand = (AbstractCommand) serializable;
                //out.writeByte(RpcProtocolV2.PROTOCOL_CODE);
                //Attribute<Byte> version = chc.channel().attr(Channel.VERSION);
                //byte ver = RpcProtocolV2.PROTOCOL_VERSION_1;
                //if (version != null && version.get() != null) {
                //    ver = version.get();
                //}
                //out.writeByte(ver);
                byteBuf.writeByte(abstractCommand.getType());
                byteBuf.writeShort(((AbstractCommand) serializable).getCommandCode().value());
                //byteBuf.writeByte(abstractCommand.getVersion());
                byteBuf.writeInt(abstractCommand.getId());
                //out.writeByte(cmd.getSerializer());
                byteBuf.writeByte(abstractCommand.getProtocolSwitch().toByte());
                if (abstractCommand instanceof RequestCommand) {
                    //timeout
                    byteBuf.writeInt(((RequestCommand) abstractCommand).getTimeout());
                }
                if (abstractCommand instanceof ResponseCommand) {
                    //response status
                    ResponseCommand response = (ResponseCommand) abstractCommand;
                    byteBuf.writeShort(response.getResponseStatus().getValue());
                }
                byteBuf.writeShort(abstractCommand.getClazzLength());
                byteBuf.writeShort(abstractCommand.getHeaderLength());
                byteBuf.writeInt(abstractCommand.getContentLength());
                if (abstractCommand.getClazzLength() > 0) {
                    byteBuf.writeBytes(abstractCommand.getClazz());
                }
                if (abstractCommand.getHeaderLength() > 0) {
                    byteBuf.writeBytes(abstractCommand.getHeader());
                }
                if (abstractCommand.getContentLength() > 0) {
                    byteBuf.writeBytes(abstractCommand.getContent());
                }
                if (abstractCommand.getProtocolSwitch().isOn(ProtocolSwitch.CRC_SWITCH_INDEX)) {
                    // compute the crc32 and write to out
                    byte[] bytes = new byte[byteBuf.readableBytes()];
                    byteBuf.getBytes(index, bytes);
                    byteBuf.writeInt(CrcUtil.crc32(bytes));
                }
            } else {
                String warnMsg = "msg type [" + serializable.getClass() + "] is not subclass of RpcCommand";
                log.warn(warnMsg);
            }
        } catch (Exception e) {
            log.error("Exception caught!", e);
            throw e;
        }
    }
}
